
#ifndef  HELPER_WATCH_STOPWATCH_H
#define  HELPER_WATCH_STOPWATCH_H

#include <list>
#include <unordered_map>
#include "IStopWatch.h"
#include "Timespan.h"

namespace helper {
namespace watch {

template<typename T>
class StopWatchSingleShot : public IStopWatch<void> {
    // INIT --enter()-->ENTERED --exit--> EXITED --reset()--> INIT
    enum State {
        INIT,
        ENTERED,
        EXITED,
    };
public:
    StopWatchSingleShot()
    {
        reset();
    }
    void reset(void) override{
        ts.start = ts.end = clock.init();
        state = INIT;
    }
    int enter(void) override {
        if(state == INIT){
            ts.start = clock.now();
            state = ENTERED;
            return 0;
        }
        return -1;
    }
    int exit(void) override{
        if(state == ENTERED){
            ts.end = clock.now();
            state = EXITED;
            return 0;
        }
        return -1;
    }

    std::string toString() const override{
        std::ostringstream oss;
        oss << "startTs:" << ts.start
            << ",endTs:" << ts.end // time_point
            << ",elapse:" <<  clock.diff(ts); // duration 
        return oss.str();
    }
private:
    T  clock;
    Timespan<typename T::TimePoint> ts;
    State state;
};

template<typename T>
class StopWatch : public IStopWatch<void> {
    //INIT --enter--> ENTERED --exit-> INIT
    enum State {
        INIT,
        ENTERED,
    };

public:
    typedef Timespan<typename T::TimePoint> TimeSpanType;

    StopWatch()
    {
        reset();
    }
    void reset(void) override{
        state = INIT;
        timespans.clear();
    }
    int enter(void) override {
        if(state == INIT){
            timespans.emplace_back(clock.now(), clock.init());
            state = ENTERED;
            return 0;
        }
        return -1;
    }
    int exit(void) override{
        if(state == ENTERED){
            TimeSpanType & last = timespans.back();
            last.end = clock.now();
            state = INIT;
            return 0;
        }
        return -1;
    }
    std::string toString() const override{
        std::ostringstream oss;
        int count = 0;
        for(auto & ts : timespans){
            oss << count++ << ">"
                << "startTs:" << ts.start
                << ",endTs:" << ts.end
                << ",elapse:" <<  clock.diff(ts)
                << ";";
        }
        return oss.str();
    }
private:
    State state;
    T  clock;
    std::list<Timespan<typename T::TimePoint>> timespans;
};

template<typename T>
class NamedStopWatchSingleShot : public IStopWatch<const std::string> {
    // INIT --enter()-->ENTERED --exit--> EXITED --reset()--> INIT
    enum State {
        INIT,
        ENTERED,
        EXITED,
    };
public:
    NamedStopWatchSingleShot()
    {
        reset();
    }
    void reset() override{
        this->name.clear();

        ts.start = ts.end = clock.init();
        state = INIT;
    }
    int enter(const std::string & name) override {
        if(state == INIT){
            this->name = name;

            ts.start = clock.now();
            state = ENTERED;
            return 0;
        }
        return -1;
    }
    int exit(const std::string & name) override{
        if(this->name != name){
            return -1;
        }

        if(state == ENTERED){
            ts.end = clock.now();
            state = EXITED;
            return 0;
        }
        return -1;
    }

    std::string toString() const override{
        std::ostringstream oss;
        oss << name << ">"
            << "startTs:" << ts.start
            << ",endTs:" << ts.end
            << ",elapse:" <<  clock.diff(ts);
        return oss.str();
    }
private:
    std::string name;

    T  clock;
    Timespan<typename T::TimePoint> ts;
    State state;
};

template<typename T>
class NamedStopWatch : public IStopWatch<const std::string> {
    //INIT --enter--> ENTERED --exit-> INIT
    enum State {
        INIT,
        ENTERED,
    };

public:
    typedef Timespan<typename T::TimePoint> TimeSpanType;

    NamedStopWatch()
    {
        reset();
    }
    void reset(void) override{
        state = INIT;
        timespans.clear();
    }
    int enter(const std::string & name) override {
        if(state == INIT){
            timespans.emplace(name, TimeSpanType(clock.now(), clock.init()));
            state = ENTERED;
            return 0;
        }
        return -1;
    }
    int exit(const std::string & name) override{
        if(state == ENTERED){
            auto iter = timespans.find(name);
            if(iter != timespans.end()){
                iter->second.end = clock.now();
                state = INIT;
                return 0;
            }
            //can not find the named-timespan
        }
        return -1;
    }
    std::string toString() const override{
        std::ostringstream oss;
        for(auto & pair : timespans){
            oss << pair.first << ">"
                << "startTs:" << pair.second.start
                << ",endTs:" << pair.second.end
                << ",elapse:" <<  clock.diff(pair.second)
                << ";";
        }
        return oss.str();
    }
private:
    State state;
    T  clock;
    std::unordered_map<std::string, Timespan<typename T::TimePoint>> timespans;
};

}// end of namespace watch
}// end of namespace helper

#endif   /* HELPER_WATCH_STOPWATCH_H */
